<?php

declare (strict_types=1);

namespace think\admin\service;

use think\admin\Exception;
use think\admin\extend\CodeExtend;
use think\admin\model\SysBlockChain;
use think\admin\model\SysMerchant;
use think\admin\Service;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;


/*
 * 蚂蚁区块链接口
 * Class BlockChainService
 */

class BlockChainService extends Service
{
    /**
     * 访问BlockToken
     * @var string
     */
    public string $blockToken;
    /**
     * 区块链数据缓存时间
     * @var integer
     */
    protected int $expire = 1800;
    /**
     * 蚂蚁区块链接口地址
     * @var string
     */
    protected string $url;

    /**
     * 链 ID (打开合约在链接上)
     * @var string
     */
    protected string $bizId;

    /**
     * 开放联盟链access-id
     * @var string
     */

    protected string $accessId;

    /**
     * mykmsKeyId
     * @var string
     */

    protected string $mykmsKeyId;

    /**
     * 蚂蚁租户ID
     * @var string
     */
    protected string $tenantId;

    /**
     * 私钥
     * @var string
     */
    protected string $private;

    /**
     * 存证Api
     * @param string $merchantId
     * @param int $uid
     * @param string $dataType
     * @param string $content
     * @param string $oldContent
     * @return array
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function existingEvidence(string $merchantId, int $uid, string $dataType, string $content, string $oldContent): array
    {
        if (!$this->checkGas($merchantId)) {
            return [false, '燃料不足,停止上链'];
        }

        $uri = '/api/contract/chainCallForBiz';
        $params = [
            'orderId' => CodeExtend::uniqidNumber(10),
            'bizid' => $this->bizId,
            'account' => 'rotoos',
            'mykmsKeyId' => $this->mykmsKeyId,
            'method' => 'DEPOSIT',
            'accessId' => $this->accessId,
            'token' => $this->blockToken,
            'gas' => 30000,
            'tenantid' => $this->tenantId,
            'content' => $content
        ];
        $params = json_encode($params);
        $result = json_decode(http_post_data($this->url . $uri, $params)[1], true);
        sleep(1);

        if ($result['success']) {
            [$state, $chainData] = $this->TransactionReceipt($this->blockToken, $result['data']);
            $gasUsed = $state ? $chainData['gasUsed'] : '';
            $blockNumber = $state ? $chainData['blockNumber'] : '';

            $dat = [
                'merchant_id' => $merchantId ?: $this->app->request->header('merchantId'),
                'created_by' => $uid,
                'data_type' => $dataType,
                'chain_type' => 'DEPOSIT',
                'md5_value' => $content,
                'content' => $oldContent,
                'hash' => $result['data'],
                'gas_used' => $gasUsed,
                'block_number' => $blockNumber,
                'code' => (int)$result['code'],
                'qrcode' => $result['code'] == 200 ? 'https://render.antfin.com/p/s/miniapp-web/?type=trans&from=antcloud&bizid=' . $this->bizId . '&hash=' . $result['data'] : ''
            ];

            if ($result['code'] == 200) {
                SysBlockChain::mk()->insert($dat);
            }

            if ($gasUsed) {
                $merchant = SysMerchant::mk()->where(['merchant_id' => $merchantId])->findOrEmpty();
                $merchant->dec('gas_total', $gasUsed)->inc('gas_used', $gasUsed)->update(['updated_by' => $uid]);
            }

            return [$result['success'], $result['success'] ? '上链成功' : '上链失败'];
        } else {
            return [$result['success'], '上链失败'];
        }
    }


    /**
     * 检测是否有足够的GAS
     * @param string $merchantId
     * @return bool
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function checkGas(string $merchantId): bool
    {
        if (!$merchantId) {
            return false;
        }

        $merchant = SysMerchant::mk()->where(['merchant_id' => $merchantId, 'is_deleted' => 0])->find();

        if ($merchant && ($merchant['gasTotal'] - $merchant['gasUsed']) >= 25000) {
            return true;
        }

        return false;
    }


    /**
     * 查询交易回执
     * @param string $token
     * @param string $hash
     * @return array
     */
    public function TransactionReceipt(string $token, string $hash): array
    {
        $uri = '/api/contract/chainCall';
        $params = [
            'bizid' => $this->bizId,
            'method' => 'QUERYRECEIPT',
            'accessId' => $this->accessId,
            'token' => $token,
            'hash' => $hash
        ];
        $result = json_decode(http_post_data($this->url . $uri, json_encode($params))[1], true);

        if ($result['success']) {
            $r = json_decode($result['data'], true);
            return [true, $r];
        } else {
            return [false, []];
        }
    }

    /**
     * 查询交易(区块值和内容)
     * @param string $token
     * @param string $hash
     * @return array
     */
    public function queryTransaction(string $token, string $hash): array
    {
        $uri = '/api/contract/chainCall';
        $params = [
            'bizid' => $this->bizId,
            'method' => 'QUERYTRANSACTION',
            'accessId' => $this->accessId,
            'token' => $token,
            'hash' => $hash
        ];
        $result = json_decode(http_post_data($this->url . $uri, json_encode($params))[1], true);

        if ($result['success']) {
            $res = json_decode($result['data'], true);
            return [true, $res];
        } else {
            return [false, []];
        }
    }


    /**
     * 查询块头
     * @param string $token
     * @param string $requestStr
     * @return mixed
     */
    public function BlockHeader(string $token, string $requestStr): mixed
    {
        $uri = '/api/contract/chainCall';
        $params = [
            'bizid' => $this->bizId,
            'requestStr' => $requestStr,
            'method' => 'QUERYBLOCK',
            'accessId' => $this->accessId,
            'token' => $token
        ];
        [$code, $result] = http_post_data($this->url . $uri, json_encode($params));
        return $result;
    }

    /**
     * 查询块体
     * @param string $token
     * @param string $requestStr
     * @return mixed
     */
    public function BlockBody(string $token, string $requestStr): mixed
    {
        $uri = '/api/contract/chainCall';
        $params = [
            'bizid' => $this->bizId,
            'requestStr' => $requestStr,
            'method' => 'QUERYBLOCKBODY',
            'accessId' => $this->accessId,
            'token' => $token
        ];
        [$code, $result] = http_post_data($this->url . $uri, json_encode($params));
        return $result;
    }

    /**
     * 查询最新块高
     * @param string $token
     * @return mixed
     */
    public function NewBlockHeight(string $token): mixed
    {
        $uri = '/api/contract/chainCall';
        $params = [
            'bizid' => $this->bizId,
            'method' => 'QUERYLASTBLOCK',
            'accessId' => $this->accessId,
            'token' => $token
        ];
        [$code, $result] = http_post_data($this->url . $uri, json_encode($params));
        return $result;
    }

    /**
     * 查询账户
     * @param string $token
     * @return mixed
     */
    public function queryAccount(string $token): mixed
    {
        $uri = '/api/contract/chainCall';
        $params = [
            'bizid' => $this->bizId,
            'requestStr' => "{\"queryAccount\":\"rotoos\"}",
            'method' => 'QUERYACCOUNT',
            'accessId' => $this->accessId,
            'token' => $token
        ];
        [$code, $result] = http_post_data($this->url . $uri, json_encode($params));
        return $result;
    }

    /**
     * 异步调用 Solidity 合约 Api
     * @param string $merchantId
     * @param string $token
     * @return void
     * @throws DataNotFoundException
     * @throws DbException
     * @throws ModelNotFoundException
     */
    public function solidityAdd(string $merchantId, string $token): void
    {
        $this->checkGas($merchantId);
        $uri = '/api/contract/chainCallForBiz';
        $params = [
            'orderId' => CodeExtend::uniqidNumber(10),
            'bizid' => $this->bizId,
            'account' => 'rotoos',
            'contractName' => '关联批次',
            'methodSignature' => 'insertPihao(string,string,string,string,string,string,string,string)',
            'inputParamListStr' => "['1000111','789878777223','我是个商品','img.jpg','8.90','20110202','商家名','2021-09-24']",
            'outTypes' => '[]',
            'mykmsKeyId' => $this->mykmsKeyId,
            'method' => 'CALLCONTRACTBIZASYNC',
            'accessId' => $this->accessId,
            'token' => $token,
            'gas' => 2000000,
            'tenantid' => $this->tenantId
        ];
        $params = json_encode($params);
        [$code, $result] = http_post_data($this->url . $uri, $params);
    }

    /**
     * 控制器初始化
     * @return void
     * @throws Exception
     */
    protected function initialize(): void
    {
        $this->url = sysconfig('ANT_CHAIN_BLOCK', 'SQM_ANT_CHAIN_BLOCK_URL');
        $this->bizId = sysconfig('ANT_CHAIN_BLOCK', 'SQM_ANT_CHAIN_BLOCK_BIZ_ID');
        $this->accessId = sysconfig('ANT_CHAIN_BLOCK', 'SQM_ANT_CHAIN_BLOCK_ACCESS_ID');
        $this->mykmsKeyId = sysconfig('ANT_CHAIN_BLOCK', 'SQM_ANT_CHAIN_BLOCK_MY_KMSKEY_ID');
        $this->tenantId = sysconfig('ANT_CHAIN_BLOCK', 'SQM_ANT_CHAIN_BLOCK_TENANT_ID');
        $this->private = sysconfig('ANT_CHAIN_BLOCK', 'SQM_ANT_CHAIN_BLOCK_PRIVATE_KEY');
        $this->blockToken = $this->app->cache->get("blockToken") ? $this->app->cache->get("blockToken") : $this->getToken();
    }

    /**
     * 蚂蚁获取Token
     */
    public function getToken()
    {
        $uri = '/api/contract/shakeHand';
        $res = [];
        $time = getUnixTimestamp();
        $data = $this->accessId . $time;
        $isOK = openssl_sign($data, $sign, $this->private, OPENSSL_ALGO_SHA256);
        if ($isOK) {
            $sig = bin2hex($sign);//转16进制
            $data = json_encode(array('accessId' => $this->accessId, 'time' => $time . '', 'secret' => $sig));
            [$return_code, $return_content] = http_post_data($this->url . $uri, $data);
            $res = json_decode($return_content, true);//echo $res['data'];//这个就是token
            if (!empty($res['data'])) {
                $this->app->cache->set("blockToken", $res['data'], $this->expire);
            }
        }
        return $this->blockToken = $res['data'];
    }
}